﻿using System;
using System.Configuration;
using System.ServiceModel;
using System.Reflection;
using System.Diagnostics;

namespace Catalyst.ServiceModelZero
{
	public abstract class BaseProxy<T, I>
		where T : BaseProxy<T, I>, new()
		where I : class
	{
		static T _instance;
		static System.ServiceModel.Channels.Binding _binding;
		static EndpointAddress _address;

		protected abstract System.ServiceModel.Channels.Binding GetBinding();
		protected abstract EndpointAddress GetEndpointAddress();

		public static I CreateChannel(System.ServiceModel.Channels.Binding binding = null, EndpointAddress endpointAddress = null)
		{
			if (_instance == null)
			{
				_instance = new T();
			}

			if (binding != null)
			{
				_binding = binding;
			}
			else
			{
				_binding = _instance.GetBinding();
			}

			if (endpointAddress != null)
			{
				_address = endpointAddress;
			}
			else
			{
				_address = _instance.GetEndpointAddress();
			}

			return ChannelFactory<I>.CreateChannel(_binding, _address);
		}

		public static void CloseChannel(I proxy)
		{
			ICommunicationObject commObject = proxy as ICommunicationObject;

			if (commObject != null && commObject.State != CommunicationState.Faulted)
			{
				commObject.Close();
			}
		}

		public static void Abort(I proxy)
		{
			ICommunicationObject commObject = proxy as ICommunicationObject;

			if (commObject != null)
			{
				commObject.Abort();
			}
		}

		protected static void FireEvent(params object[] args)
		{
			FireEvent(null, null, args);
		}

		protected static void FireEvent(I sharedProxy, params object[] args)
		{
			InvokeMethod<object>(sharedProxy, args);
		}

		protected static void FireEvent(System.ServiceModel.Channels.Binding binding, params object[] args)
		{
			FireEvent(binding, null, args);
		}

		protected static void FireEvent(EndpointAddress endpointAddress, params object[] args)
		{
			FireEvent(null, endpointAddress, args);
		}

		protected static void FireEvent(System.ServiceModel.Channels.Binding binding, EndpointAddress endpointAddress, params object[] args)
		{
			InvokeMethod<object>(binding, endpointAddress, args);
		}

		protected static S InvokeMethod<S>(params object[] args)
		{
			return InvokeMethod<S>(null, null, args);
		}

		protected static S InvokeMethod<S>(EndpointAddress endpointAddress, params object[] args)
		{
			return InvokeMethod<S>(null, endpointAddress, args);
		}

		protected static S InvokeMethod<S>(System.ServiceModel.Channels.Binding binding, params object[] args)
		{
			return InvokeMethod<S>(binding, null, args);
		}

		protected static S InvokeMethod<S>(I sharedProxy, params object[] args)
		{
			S returnValue = default(S);

			bool proxyNotShared = false;

			try
			{
				if (sharedProxy == null)
				{
					proxyNotShared = true;
					sharedProxy = CreateChannel();
				}

				returnValue = invokeMethod<S>(sharedProxy, args);
			}
			catch
			{
				Abort(sharedProxy);
				throw;
			}
			finally
			{
				if (proxyNotShared)
				{
					CloseChannel(sharedProxy);
				}
			}

			return returnValue;
		}

		protected static S InvokeMethod<S>(System.ServiceModel.Channels.Binding binding, EndpointAddress endpointAddress, params object[] args)
		{
			S returnValue = default(S);

			I proxy = null;

			try
			{
				proxy = CreateChannel(binding, endpointAddress);
				returnValue = invokeMethod<S>(proxy, args);
			}
			//catch (TimeoutException) { }
			catch
			{
				Abort(proxy);
				throw;
			}
			finally
			{
				CloseChannel(proxy);
			}

			return returnValue;
		}

		static S invokeMethod<S>(I proxy, object[] args)
		{
			S returnValue = default(S);

			Type type = typeof(I);
			MethodInfo methodInfo = type.GetMethod(getMethodName());

			object obj = null;

			try
			{
				if (methodInfo.ReturnType != typeof(void))
				{
					returnValue = (S)methodInfo.Invoke(proxy, args);
				}
				else
				{
					methodInfo.Invoke(proxy, args);
				}
			}
			catch (Exception e)
			{
				Trace.WriteLine(e.Message);
			}

			return returnValue;
		}

		static string getMethodName()
		{
			StackTrace stackTrace = new StackTrace();

			Type proxyType = typeof(T);
			Type serviceType = typeof(I);

			Type implementedType = typeof(BaseProxy<,>).MakeGenericType(proxyType, serviceType);

			foreach (StackFrame frame in stackTrace.GetFrames())
			{
				MethodBase methodBase = frame.GetMethod();

				if (methodBase.ReflectedType.BaseType.IsGenericType)
				{
					Type[] genericTypes = methodBase.ReflectedType.BaseType.GetGenericArguments();

					//if (methodBase.ReflectedType.BaseType == implementedType)
					if (genericTypes.Length == 2 && genericTypes[0] == proxyType && genericTypes[1] == serviceType)
					{
						return methodBase.Name;
					}
				}
			}

			return null;
		}
	}
}
